package org.kisekiproject.evaluator {
	import org.kisekiproject.evaluator.errors.ExpressionError;
	import org.kisekiproject.utils.StringUtil;

	/**
	 * @author mikael
	 */
	public class Tokenizer {

		private var _pos:int;
		private var _string:String;

		/**
		 * Constructor.
		 */
		public function Tokenizer():void {
		}

		/**
		 * Tokenize string.
		 */
		public function tokenize(s:String):Array {
			var res:Array=new Array();

			_string=s;
			_pos=0;

			while(_pos<_string.length) {
				if (StringUtil.isWhitespace(_string.charAt(_pos)))
					_pos++;

				else
					res.push(getNextToken());
			}

			return res;
		}

		/**
		 * Get next token.
		 */
		private function getNextToken():Token {
			var opChars:String="+-*/.[]!=<>&|";

			if (reMatch(_string.charAt(_pos),/\.?[0-9]/))
				 return extractNumber();

			else if (reMatch(_string.charAt(_pos),/[a-zA-Z_$]/))
				 return extractSymbol();

			else if (_string.charAt(_pos)=='"')
				return extractString();

			else if (opChars.indexOf(_string.charAt(_pos))>=0)
				 return extractOperator();

			else if ("()".indexOf(_string.charAt(_pos))>=0)
				return extractParentheses();

			else throw new ExpressionError("Unexpected token",_string,_pos);
		}

		/**
		 * Extract parentheses.
		 */
		private function extractParentheses():Token {
			var p:int=_pos;

			switch (_string.charAt(_pos++)) {
				case "(":
					return new Token(Token.LEFT_PARENTHESES,null,p);
					break;
					
				case ")":
					return new Token(Token.RIGHT_PARENTHESES,null,p);
					break;
			}

			throw new ExpressionError("Unexpected token",_string,_pos);
		}

		/**
		 * Extract number.
		 */
		private function extractNumber():Token {
			var p:int=_pos;
			var s:String=extractRegExp(/[0-9]*(\.[0-9]+)?/);

			return new Token(Token.NUMBER,Number(s),p);
		}

		/**
		 * Extract Symbol.
		 */
		private function extractSymbol():Token {
			var p:int=_pos;
			var s:String=extractRegExp(/[A-Za-z_$]?[A-Za-z0-9_$]*/);

			if (s=="if" || s=="else")
				return new Token(Token.FLOW,s,p);

			return new Token(Token.SYMBOL,s,p);
		}

		/**
		 * Extract String.
		 */
		private function extractString():Token {
			var p:int=_pos;
			var s:String=extractRegExp(/"[^"]*"/);

			return new Token(Token.STRING,s.substr(1,s.length-2),p);
		}

		/**
		 * Extract String.
		 */
		private function extractOperator():Token {

			// Long must be before short.
			var operators:Array=["==","!=","<=",">=","!","*","%","(",")","[","]","+","=","<",">",".","-","/","&&","||"];
			var p:int=_pos;

			for each (var op:String in operators)
				if (_string.substr(_pos,op.length)==op) {
					_pos+=op.length;

					return new Token(Token.BINARY_OPERATOR, op, p);
				}

			throw new ExpressionError("Unexpected token",_string,_pos);
		}

		/**
		 * Extract reg exp and advance position pointer.
		 */
		private function extractRegExp(re:RegExp):String {
			var cand:String=_string.substr(_pos);

			//trace("cand: "+cand);
			var a:Array=cand.match(re);

			//trace("matches: "+a);
			if (a.length<1)
				throw new Error("Something is strange");

			var match:String=a[0];

			_pos+=match.length;

			return match;
		}

		/**
		 * Reg exp match.
		 */
		private static function reMatch(s:String, re:RegExp):Boolean {
			 return s.match(re)!=null;
		}
	}
}
